package main

import (
	"archive/zip"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"embed"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

const (
	jdkZipPath     = "resource/jdk.zip"
	jarPath        = "#{appName}"
	pluginDir      = "plugins"
	dateFormat     = "2006-01-02 15:04:05"
	validStartDate = "#{validStartDate}"
	validEndDate   = "#{validEndDate}"
	hexKey         = "#{hexKey}"
	hexIV          = "#{hexIV}"
	code           = "#{code}"
)

//go:embed resource/*
//go:embed #{appName}
//go:embed plugins/*.x
var resource embed.FS

func main() {
	// 检查命令行参数
	args := os.Args[1:]
	if len(args) > 0 {
		switch args[0] {
		case "add-plugin":
			if len(args) < 2 {
				fmt.Println("Usage: app add-plugin <plugin-file>")
				os.Exit(1)
			}
			if err := addPlugin(args[1]); err != nil {
				fmt.Printf("Failed to add plugin: %v\n", err)
				os.Exit(1)
			}
			fmt.Printf("Plugin %s added successfully\n", args[1])
			return
		case "remove-plugin":
			if len(args) < 2 {
				fmt.Println("Usage: app remove-plugin <plugin-name>")
				os.Exit(1)
			}
			if err := removePlugin(args[1]); err != nil {
				fmt.Printf("Failed to remove plugin: %v\n", err)
				os.Exit(1)
			}
			fmt.Printf("Plugin %s removed successfully\n", args[1])
			return
		case "list-plugins":
			if err := listPlugins(); err != nil {
				fmt.Printf("Failed to list plugins: %v\n", err)
				os.Exit(1)
			}
			return
		}
	}

	license, err := readLicense()

	if err != nil {
		fmt.Println(err.Error())
	}

	duration, extArgs, err := checkDate(license)
	if err != nil {
		exitWithMsg(err.Error())
	}

	jdkPath := filepath.Join(os.TempDir(), "deploy", hex.EncodeToString([]byte(code))[:10], "jdk")

	if err := preEnv(jdkPath); err != nil {
		exitWithMsg("pre")
	}

	// 提取插件文件
	if err := extractPlugins(filepath.Join(filepath.Dir(os.Args[0]), pluginDir)); err != nil {
		fmt.Printf("Warning: Failed to extract plugins: %v\n", err)
	}

	if err := runApp(jdkPath, extArgs, duration); err != nil {
		exitWithMsg("run")
	}
}

// addPlugin 添加插件
func addPlugin(pluginFile string) error {
	// 获取可执行文件所在目录
	exeDir := filepath.Dir(os.Args[0])
	pluginsDir := filepath.Join(exeDir, pluginDir)
	
	// 创建插件目录（如果不存在）
	if err := os.MkdirAll(pluginsDir, os.ModePerm); err != nil {
		return fmt.Errorf("failed to create plugins directory: %v", err)
	}
	
	// 检查插件文件是否存在
	if _, err := os.Stat(pluginFile); os.IsNotExist(err) {
		return fmt.Errorf("plugin file does not exist: %s", pluginFile)
	}
	
	// 获取插件文件名
	pluginName := filepath.Base(pluginFile)
	
	// 检查是否为.x文件
	if filepath.Ext(pluginName) != ".x" {
		return fmt.Errorf("plugin file must have .x extension")
	}
	
	// 复制插件文件到插件目录
	pluginPath := filepath.Join(pluginsDir, pluginName)
	
	// 读取插件文件
	data, err := os.ReadFile(pluginFile)
	if err != nil {
		return fmt.Errorf("failed to read plugin file: %v", err)
	}
	
	// 写入插件文件
	if err := os.WriteFile(pluginPath, data, os.ModePerm); err != nil {
		return fmt.Errorf("failed to write plugin file: %v", err)
	}
	
	return nil
}

// removePlugin 移除插件
func removePlugin(pluginName string) error {
	// 获取可执行文件所在目录
	exeDir := filepath.Dir(os.Args[0])
	pluginsDir := filepath.Join(exeDir, pluginDir)
	
	// 构建插件文件路径
	pluginPath := filepath.Join(pluginsDir, pluginName)
	
	// 检查插件文件是否存在
	if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
		return fmt.Errorf("plugin does not exist: %s", pluginName)
	}
	
	// 删除插件文件
	if err := os.Remove(pluginPath); err != nil {
		return fmt.Errorf("failed to remove plugin: %v", err)
	}
	
	return nil
}

// listPlugins 列出所有插件
func listPlugins() error {
	// 获取可执行文件所在目录
	exeDir := filepath.Dir(os.Args[0])
	pluginsDir := filepath.Join(exeDir, pluginDir)
	
	// 检查插件目录是否存在
	if _, err := os.Stat(pluginsDir); os.IsNotExist(err) {
		fmt.Println("No plugins directory found")
		return nil
	}
	
	// 读取插件目录
	entries, err := os.ReadDir(pluginsDir)
	if err != nil {
		return fmt.Errorf("failed to read plugins directory: %v", err)
	}
	
	fmt.Println("Installed plugins:")
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		
		// 只显示.x文件
		if filepath.Ext(entry.Name()) == ".x" {
			info, _ := entry.Info()
			fmt.Printf("- %s (size: %d bytes)\n", entry.Name(), info.Size())
		}
	}
	
	return nil
}

func readLicense() (*License, error) {
	exePath, _ := os.Executable()
	cipherData, err := os.ReadFile(filepath.Join(filepath.Dir(exePath), "key.x"))

	if err != nil {
		return nil, fmt.Errorf("license error")
	}

	key, _ := hex.DecodeString(hexKey)
	iv, _ := hex.DecodeString(hexIV)
	plain, err := decryptAesCbc(cipherData, key, iv)

	if err != nil {
		return nil, fmt.Errorf("license error")
	}

	var lcs License
	err = json.Unmarshal(plain, &lcs)

	if err != nil {
		return nil, fmt.Errorf("license error")
	}

	if code != lcs.Code {
		return nil, fmt.Errorf("license error")
	}

	return &lcs, nil
}

func preEnv(jdkPath string) error {
	if err := os.RemoveAll(jdkPath); err != nil {
		return err
	}

	// 获取当前可执行文件的目录
	exePath, _ := os.Executable()
	exeDir := filepath.Dir(exePath)

	jdkZip, err := resource.ReadFile(jdkZipPath)
	if err != nil {
		return err
	}

	// 解压到可执行文件所在目录的 jdk 子目录中
	if err := unzip4Bytes(jdkZip, filepath.Join(exeDir, "jdk")); err != nil {
		return err
	}

	appJar, err := resource.ReadFile(jarPath)
	if err != nil {
		return err
	}

	// 修改写入的文件名为app.jar，无论原始文件名是什么
	if err := os.WriteFile(filepath.Join(exeDir, "jdk", "bin", "app.jar"), appJar, os.ModePerm); err != nil {
		return err
	}

	return nil
}

// extractPlugins 提取插件目录下的所有插件文件
func extractPlugins(pluginPath string) error {
	// 创建插件目录
	if err := os.MkdirAll(pluginPath, os.ModePerm); err != nil {
		return fmt.Errorf("failed to create plugin directory: %v", err)
	}

	// 检查是否存在插件目录
	pluginEntries, err := resource.ReadDir(pluginDir)
	if err != nil {
		// 插件目录不存在，这是正常的，直接返回
		fmt.Println("No embedded plugins found")
		return nil
	}

	pluginCount := 0
	// 遍历并提取每个插件
	for _, entry := range pluginEntries {
		if entry.IsDir() {
			continue
		}

		pluginName := entry.Name()
		// 只处理.x后缀的加密插件文件
		if filepath.Ext(pluginName) != ".x" {
			continue
		}
		
		// 检查插件是否已存在
		pluginFilePath := filepath.Join(pluginPath, pluginName)
		if _, err := os.Stat(pluginFilePath); err == nil {
			// 插件已存在，跳过
			fmt.Printf("Plugin %s already exists, skipping\n", pluginName)
			continue
		}
		
		fmt.Printf("Extracting plugin: %s\n", pluginName)

		// 读取插件数据
		pluginData, err := resource.ReadFile(filepath.Join(pluginDir, pluginName))
		if err != nil {
			fmt.Printf("Failed to read plugin %s: %v\n", pluginName, err)
			continue
		}

		// 写入插件文件
		if err := os.WriteFile(pluginFilePath, pluginData, os.ModePerm); err != nil {
			fmt.Printf("Failed to write plugin %s: %v\n", pluginName, err)
			continue
		}

		pluginCount++
		fmt.Printf("Extracted plugin: %s\n", pluginName)
	}
	
	fmt.Printf("Plugin extraction complete, extracted %d plugins\n", pluginCount)

	return nil
}

func runApp(jdkPath, extArgs string, duration time.Duration) error {
	args := " #{jarArgs} " + extArgs + " -jar app.jar"
	cmd := exec.Command(filepath.Join(jdkPath, "bin", "java"), strings.Fields(args)...)
	cmd.Dir = filepath.Join(jdkPath, "bin")
	cmd.Stdin = bytes.NewReader(encodeKey())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if duration > 0 {
		time.AfterFunc(duration, func() {
			fmt.Println("stop process")
			err := cmd.Process.Kill()
			if err != nil {
				fmt.Println("kill process error")
			}
			os.Exit(666)
		})
	}

	return cmd.Run()
}

func encodeKey() []byte {
	return bytes.Join([][]byte{
		xKey.algorithm, {13, 10},
		xKey.keysize, {13, 10},
		xKey.ivsize, {13, 10},
		xKey.password, {13, 10},
	}, []byte{})
}

func checkDate(lcs *License) (time.Duration, string, error) {
	vsd := ""
	ved := ""
	args := ""

	if lcs == nil {
		fmt.Println("failed to read key.x -> use app key info ")
		vsd = validStartDate
		ved = validEndDate
	} else {
		fmt.Println("success to read key.x -> use license key info ")
		vsd = lcs.ValidStartDate
		ved = lcs.ValidEndDate
		args = lcs.Args
	}

	now := time.Now()
	loc := time.Local

	start, err := time.ParseInLocation(dateFormat, vsd, loc)
	if err != nil {
		return -1, args, fmt.Errorf("date parse error")
	}

	if now.Before(start) {
		return -1, args, fmt.Errorf("date expired")
	}

	if ved == "" {
		return -1, args, nil
	}

	end, err := time.ParseInLocation(dateFormat, ved, loc)
	if err != nil {
		return -1, args, fmt.Errorf("date parse error")
	}

	if now.After(end) {
		return -1, args, fmt.Errorf("date expired")
	}

	return end.Sub(now), args, nil
}

func unPKCS5Padding(data []byte) ([]byte, error) {
	if len(data) == 0 {
		return nil, fmt.Errorf("input is empty")
	}

	padLen := int(data[len(data)-1])
	if padLen <= 0 || padLen > len(data) {
		return nil, fmt.Errorf("invalid padding size")
	}

	for _, v := range data[len(data)-padLen:] {
		if int(v) != padLen {
			return nil, fmt.Errorf("invalid padding bytes")
		}
	}

	return data[:len(data)-padLen], nil
}

func decryptAesCbc(cipherData, key, iv []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher")
	}

	if len(cipherData)%block.BlockSize() != 0 {
		return nil, fmt.Errorf("ciphertext length is not a multiple of block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)
	plain := make([]byte, len(cipherData))
	mode.CryptBlocks(plain, cipherData)

	return unPKCS5Padding(plain)
}

func unzip4Bytes(data []byte, dest string) error {
	reader := bytes.NewReader(data)
	zr, err := zip.NewReader(reader, int64(len(data)))
	if err != nil {
		return fmt.Errorf("create zip reader: %w", err)
	}

	for _, f := range zr.File {
		if strings.Contains(f.Name, "..") {
			return fmt.Errorf("invalid file path: %s", f.Name)
		}

		target := filepath.Join(dest, f.Name)
		if f.FileInfo().IsDir() {
			if err := os.MkdirAll(target, os.ModePerm); err != nil {
				return fmt.Errorf("mkdir: %w", err)
			}
			continue
		}

		if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {
			return fmt.Errorf("mkdir parent: %w", err)
		}

		src, err := f.Open()
		if err != nil {
			return fmt.Errorf("open zip file: %w", err)
		}

		dst, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			err := src.Close()
			if err != nil {
				return err
			}
			return fmt.Errorf("create file: %w", err)
		}

		if _, err := io.Copy(dst, src); err != nil {
			err := src.Close()
			if err != nil {
				return err
			}
			err = dst.Close()
			if err != nil {
				return err
			}
			return fmt.Errorf("copy file: %w", err)
		}

		err = src.Close()
		if err != nil {
			return err
		}

		err = dst.Close()
		if err != nil {
			return err
		}
	}

	return nil
}

func exitWithMsg(msg string) {
	fmt.Println(msg)
	os.Exit(666)
}

type XKey struct {
	algorithm []byte
	keysize   []byte
	ivsize    []byte
	password  []byte
}

type License struct {
	Code           string `json:"code"`
	ValidStartDate string `json:"validStartDate"`
	ValidEndDate   string `json:"validEndDate"`
	Args           string `json:"args"`
}

var xKey = XKey{
	algorithm: []byte{#{xKey.algorithm}},
	keysize:   []byte{#{xKey.keysize}},
	ivsize:    []byte{#{xKey.ivsize}},
	password:  []byte{#{xKey.password}},
}